#! /usr/bin/python3
import os.path
import sys
import shlex
import re

from headerutils import *

header_roots = { }
extra_edges = list()
verbose = False
verbosity = 0
nodes = list()

def unpretty (name):
  if name[-2:] == "_h":
    name = name[:-2] + ".h"
  return name.replace("_", "-")

def pretty_name (name):
  name = os.path.basename (name)
  return name.replace(".","_").replace("-","_").replace("/","_").replace("+","_");

depstring = ("In file included from", "                 from")

# indentation indicates nesting levels of included files
ignore = [ "coretypes_h",
             "insn_modes_h",
             "signop_h",
             "wide_int_h",
             "wide_int_print_h",
             "insn_modes_inline_h",
             "machmode_h",
             "double_int_h",
             "real_h",
             "fixed_value_h",
             "hash_table_h",
               "statistics_h",
               "ggc_h",
               "vec_h",
               "hashtab_h",
               "inchash_h",
               "mem_stats_traits_h",
               "hash_map_traits_h",
               "mem_stats_h",
               "hash_map_h",
             "hash_set_h",
             "input_h",
               "line_map_h",
             "is_a_h",
           "system_h",
           "config_h" ]

def process_log_file (header, logfile):
  if header_roots.get (header) != None:
    print ("Error: already processed log file: " + header + ".log")
    return
  hname = pretty_name (header)
  header_roots[hname] = { }
  
  sline = list();
  incfrom = list()
  newinc = True
  for line in logfile:
    if len (line) > 21 and line[:21] in depstring:
      if newinc:
        incfrom = list()
        newinc = False
      fn = re.findall(r".*/(.*?):", line)
      if len(fn) != 1:
        continue
      if fn[0][-2:] != ".h":
        continue
      n = pretty_name (fn[0])
      if n not in ignore:
        incfrom.append (n)
      continue
    newinc = True
    note = re.findall (r"^.*note: (.*)", line)
    if len(note) > 0:
      sline.append (("note", note[0]))
    else:
      err_msg = re.findall (r"^.*: error: (.*)", line)
      if len(err_msg) == 1:
        msg = err_msg[0]
        if (len (re.findall("error: forward declaration", line))) != 0:
          continue
        path = re.findall (r"^(.*?):.*error: ", line)
        if len(path) != 1:
          continue
        if path[0][-2:] != ".h":
          continue
        fname = pretty_name (path[0])
        if fname in ignore or fname[0:3] == "gt_":
          continue
        sline.append (("error", msg, fname, incfrom))

  print (str(len(sline)) + " lines to process")
  lastline = "note"
  for line in sline:
    if line[0] != "note" and lastline[0] == "error":
      fname = lastline[2]
      msg = lastline[1]
      incfrom = lastline[3]
      string = ""
      ofname = fname
      if len(incfrom) != 0:
        for t in incfrom:
          string = string + t + " : "
          ee = (fname, t)
          if ee not in extra_edges:
            extra_edges.append (ee)
          fname = t
          print (string)

      if hname not in nodes:
        nodes.append(hname)
      if fname not in nodes:
        nodes.append (ofname)
      for y in incfrom:
        if y not in nodes:
          nodes.append (y)


      if header_roots[hname].get(fname) == None:
        header_roots[hname][fname] = list()
      if msg not in header_roots[hname][fname]:
        print (string + ofname + " : " +msg)
        header_roots[hname][fname].append (msg)
    lastline = line;


dotname = "graph.dot"
graphname = "graph.png"


def build_dot_file (file_list):
  output = open(dotname, "w")
  output.write ("digraph incweb {\n");
  for x in file_list:
    if os.path.exists (x) and x[-4:] == ".log":
      header =  x[:-4]
      logfile = open(x).read().splitlines()
      process_log_file (header, logfile)
    elif os.path.exists (x + ".log"):
      logfile = open(x + ".log").read().splitlines()
      process_log_file (x, logfile)

  for n in nodes:
    fn = unpretty(n)
    label = n + " [ label = \"" + fn  + "\" ];"
    output.write (label + "\n")
    if os.path.exists (fn):
      h = open(fn).read().splitlines()
      for l in h:
        t = find_pound_include (l, True, False)
        if t != "":
          t = pretty_name (t)
          if t in ignore or t[-2:] != "_h":
            continue
          if t not in nodes:
            nodes.append (t)
          ee = (t, n)
          if ee not in extra_edges:
            extra_edges.append (ee)

  depcount = list()
  for h in header_roots:
    for dep in header_roots[h]:
      label = " [ label = "+ str(len(header_roots[h][dep])) + " ];"
      string = h + " -> " + dep + label
      output.write (string + "\n");
      if verbose:
        depcount.append ((h, dep, len(header_roots[h][dep])))

  for ee in extra_edges:
    string = ee[0] + " -> " + ee[1] + "[ color=red ];"
    output.write (string + "\n");

  
  if verbose:
    depcount.sort(key=lambda tup:tup[2])
    for x in depcount:
      print (" ("+str(x[2])+ ") : " + x[0] + " -> " + x[1])
      if (x[2] <= verbosity):
        for l in header_roots[x[0]][x[1]]:
          print ("            " + l)

  output.write ("}\n");


files = list()
dohelp = False
edge_thresh = 0
for arg in sys.argv[1:]:
  if arg[0:2] == "-o":
    dotname = arg[2:]+".dot"
    graphname = arg[2:]+".png"
  elif arg[0:2] == "-h":
    dohelp = True
  elif arg[0:2] == "-v":
    verbose = True
    if len(arg) > 2:
      verbosity = int (arg[2:])
      if (verbosity == 9):
        verbosity = 9999
  elif arg[0:1] == "-":
    print ("Unrecognized option " + arg)
    dohelp = True
  else:
    files.append (arg)
    
if len(sys.argv) == 1:
  dohelp = True

if dohelp:
  print ("Parses the log files from the reduce-headers tool to generate")
  print ("dependency graphs for the include web for specified files.")
  print ("Usage:  [-nnum] [-h] [-v[n]] [-ooutput] file1 [[file2] ... [filen]]")
  print ("       -ooutput : Specifies output to output.dot and output.png")
  print ("                  Defaults to 'graph.dot and graph.png")
  print ("       -vn : verbose mode, shows the number of connections, and if n")
  print ("             is specified, show the messages if # < n. 9 is infinity")
  print ("       -h : help")
else:
  print (files)
  build_dot_file (files)
  os.system ("dot -Tpng " + dotname + " -o" + graphname)


